Imports System
Imports atcData
Imports atcUtility
Imports MapWinUtility
Imports System.Collections.ObjectModel

''' <summary>
''' Class for all classes to read GSSHA data
''' </summary>
''' <remarks>
''' Utilizing HSPFBinary library developed by ATC (source code included, stripped of other dependencies)
''' See http://svn.mapwindow.org/svnroot/BASINS40/atcTimeseriesGSSHA
''' </remarks>
Public Class clsDataGSSHA
    Inherits clsData

    Private mNumRecords As Long
    Private mDatasetNum As Integer = 0
    Private mRecordNumDS As Integer = -1
    Private mGSSHARead As atcTimeseriesGSSHA.atcTimeseriesGSSHA = Nothing

    Private hasMultipleScenarios As Boolean = False 'if true will prepend scenario to pcode

    ''' <summary>
    ''' Instantiate clsData, assign filename, open file, set filelength and fieldnames
    ''' </summary>
    ''' <param name="_Filename">Name of TS file to read</param>
    Public Overrides Sub Initialize(ByVal _Filename As String)
        MyBase.Initialize(_Filename)
        mDataType = enumDataType.GSSHA
        mFieldNames.Clear()
        mTableNames.Clear()
        mTableNames.Add(IO.Path.GetFileName(_Filename))
        mStationIDs.Clear()
        mPCodes.Clear()
        mMinDate = Date.MaxValue
        mMaxDate = Date.MinValue
        mNumRecords = 0
        mFieldNames.AddRange(New String() {"Station_ID", "PCode", "Date_Time", "Result"})
    End Sub

    ''' <summary>
    ''' It takes a long time to open a large TS file; delay it until caller really needs some data
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub Initialize2()
        mGSSHARead = New atcTimeseriesGSSHA.atcTimeseriesGSSHA
        mGSSHARead.Open(mFilename)

        Dim lastPct As Integer = -1
        For i As Integer = 0 To mGSSHARead.DataSets.Count - 1
            Dim ts As atcData.atcTimeseries = mGSSHARead.DataSets(i)
            With ts
                If Not mStationIDs.Contains(StationID_DS(i)) Then mStationIDs.Add(StationID_DS(i))
                If Not mPCodes.Contains(PCode_DS(i)) Then mPCodes.Add(PCode_DS(i))
            End With
        Next

        'if any station IDs are associated with multiple DSNs add that as an additional item: ID (All DSNs)
        'also determine if the file contains multiple scenarios (so know whether to prepend scenario for PCode)

        Dim dictStationIDs As New Generic.Dictionary(Of String, Integer)
        Dim lstScenarios As New Generic.List(Of String)
        For i As Integer = 0 To mGSSHARead.DataSets.Count - 1
            Dim ts As atcData.atcTimeseries = mGSSHARead.DataSets(i)
            With ts
                If True OrElse .numValues > 0 Then '.numValues triggers reading entire dataset--avoid this because is too slow

                    Dim Scenario As String = Proper(.Attributes.GetValue("Scenario")).Trim
                    If Not lstScenarios.Contains(Scenario) Then lstScenarios.Add(Scenario)

                    Dim StaID As String = StationID(i)

                    If Not dictStationIDs.ContainsKey(StaID) Then dictStationIDs.Add(StaID, 0)
                    dictStationIDs(StaID) += 1

                End If
            End With
        Next

        For Each kv As KeyValuePair(Of String, Integer) In dictStationIDs
            If kv.Value > 1 Then
                'following will be used to let users import data from multiple DSNs for a given Location ID
                Dim StaID As String = kv.Key & " (All DSNs)"
                If Not mStationIDs.Contains(StaID) Then mStationIDs.Add(StaID)
            End If
        Next

        hasMultipleScenarios = lstScenarios.Count > 1
        mDatasetNum = 0
    End Sub

    Public Overrides Sub Rewind()
        MyBase.Rewind()
        mDatasetNum = 0
        mRecordNumDS = -1
    End Sub

    ''' <summary>
    ''' Currently active station ID (may allow some sources to avoid long sequential reads)
    ''' </summary>
    Public Overrides Property ActiveStationID() As String
        Get
            Return MyBase.ActiveStationID
        End Get
        Set(ByVal value As String)
            MyBase.ActiveStationID = value
            If mGSSHARead Is Nothing Then Initialize2()
            For i As Integer = 0 To mGSSHARead.DataSets.Count - 1
                If StationID_DS(i) = value OrElse StationID_DSAll(i) = value Then
                    mDatasetNum = i
                    mRecordNumDS = -1
                    Exit For
                End If
            Next
        End Set
    End Property

    ''' <summary>
    ''' Return the list of unique pcodes
    ''' </summary>
    ''' <returns></returns>
    Public Overrides Function PCodes() As Generic.List(Of String)
        If mGSSHARead Is Nothing Then Initialize2()
        Return mPCodes
    End Function

    ''' <summary>
    ''' Read next line from file into memory, storing into mItems array
    ''' Also save mLastLineRead for error reporting
    ''' Automatically advance through all datasets
    ''' </summary>
    ''' <returns>True if successful, false if eof reached and no more data</returns>
    Public Overrides Function Read() As Boolean
        Try
            If Not MyBase.Read() Then Return False
            'If mRecordNum > mNumRecords - 1 Then Return False
            mItems.Clear()
            If mGSSHARead Is Nothing Then Initialize2()
            Dim ts As atcData.atcTimeseries = mGSSHARead.DataSets(mDatasetNum)
            mRecordNumDS += 1
            If mRecordNumDS > ts.numValues - 1 Then
                mDatasetNum += 1
                If mActiveStationIDSet Then                    'search for next dataset having this station id
                    While mDatasetNum <= mGSSHARead.DataSets.Count - 1 AndAlso StationID_DS(mDatasetNum) <> mActiveStationID AndAlso StationID_DSAll(mDatasetNum) <> mActiveStationID
                        mDatasetNum += 1
                    End While
                End If

                If mDatasetNum > mGSSHARead.DataSets.Count - 1 Then Return False
                mRecordNumDS = 0
                ts = mGSSHARead.DataSets(mDatasetNum)
            End If

            If ts.numValues > 0 Then
                mNumRecords += ts.numValues
                mItems.Add(StationID(mDatasetNum))
                mItems.Add(PCode_DS(mDatasetNum))
                mItems.Add(RoundDateTime(Date.FromOADate(ts.Dates.Value(mRecordNumDS))).ToString(MyDateTimeFormat))
                Dim v As Double = ts.Value(mRecordNumDS + 1) 'per email from Mark 12/6/07 need to use next index
                If (Double.IsNaN(v) Or Double.IsInfinity(v)) Then mItems.Add("") Else mItems.Add(Math.Round(v, 5))
            End If
            Return True
        Catch ex As Exception
            ErrorMsg(, ex)
            Return False
        End Try
    End Function

    ''' <summary>
    ''' Return string identifying specific DSN (for those who pay more attention to DSN than anything else)
    ''' </summary>
    ''' <remarks>Cache results to improve performance</remarks>
    Private Function StationID(DatasetNum As Integer) As String
        Static LastKey As Integer = -1
        Static CachedValue As String = ""
        If DatasetNum = LastKey Then Return CachedValue
        If mGSSHARead Is Nothing Then Initialize2()
        With mGSSHARead.DataSets(DatasetNum).Attributes
            Dim StaID As String = Microsoft.VisualBasic.Strings.Left(.GetValue("Location").ToString.ToUpper(), 15)
            If StaID = "" Then
                Dim DsnID As String = .GetValue("ID")
                StaID = String.Format("(DSN {0})", DsnID)
            End If
            CachedValue = StaID
            LastKey = DatasetNum
            Return StaID
        End With
    End Function

    ''' <summary>
    ''' Return string identifying specific DSN (for those who pay more attention to DSN than anything else)
    ''' </summary>
    ''' <remarks>Cache results to improve performance</remarks>
    Private Function StationID_DS(DatasetNum As Integer) As String
        Static LastKey As Integer = -1
        Static CachedValue As String = ""
        If DatasetNum = LastKey Then Return CachedValue
        If mGSSHARead Is Nothing Then Initialize2()
        With mGSSHARead.DataSets(DatasetNum).Attributes
            Dim StaID As String = Microsoft.VisualBasic.Strings.Left(.GetValue("Location").ToString.ToUpper(), 15)
            If StaID <> "" Then StaID &= " "
            Dim DsnID As String = .GetValue("ID")
            StaID &= String.Format("(DSN {0})", DsnID)
            CachedValue = StaID
            LastKey = DatasetNum
            Return StaID
        End With
    End Function

    ''' <summary>
    ''' Return string that can be used to select a given location in multiple DSNs (e.g., multiple constituents)
    ''' </summary>
    ''' <remarks>Cache results to improve performance</remarks>
    Private Function StationID_DSAll(DatasetNum As Integer) As String
        Static LastKey As Integer = -1
        Static CachedValue As String = ""
        If DatasetNum = LastKey Then Return CachedValue
        If mGSSHARead Is Nothing Then Initialize2()
        With mGSSHARead.DataSets(DatasetNum).Attributes
            Dim StaID As String = Microsoft.VisualBasic.Strings.Left(.GetValue("Location").ToString.ToUpper(), 15)
            If StaID <> "" Then StaID &= " "
            Dim DsnID As String = .GetValue("ID")
            StaID &= "(All DSNs)"
            CachedValue = StaID
            LastKey = DatasetNum
            Return StaID
        End With
    End Function

    Private Function PCode_DS(DatasetNum As Integer) As String
        Static LastKey As Integer = -1
        Static CachedValue As String = ""
        If DatasetNum = LastKey Then Return CachedValue
        If mGSSHARead Is Nothing Then Initialize2()
        With mGSSHARead.DataSets(DatasetNum).Attributes
            Dim Scenario As String = Proper(.GetValue("Scenario")).Trim
            If Scenario <> "" Then Scenario &= " "
            Dim PCode As String = ""
            If hasMultipleScenarios Then
                PCode = Scenario & .GetValue("Constituent")
            Else
                PCode = .GetValue("Constituent")
            End If
            PCode = Microsoft.VisualBasic.Strings.Left(PCode.ToUpper, 10)
            CachedValue = PCode
            LastKey = DatasetNum
            Return PCode
        End With
    End Function

    ''' <summary>
    ''' Get data table containing time series data for a specified station id and pcode
    ''' Table will contain date-time field and result field
    ''' </summary>
    Public Overrides Function GetDataTable(ByVal StationID As String, ByVal PCode As String) As DataTable
        If Not mStationIDs.Contains(StationID) Then
            Throw New ApplicationException("WDM file does not contain the station: " & StationID)
            Return Nothing
        End If
        Dim itemNum As Integer = -1
        If Not mPCodes.Contains(PCode) Then
            Throw New ApplicationException("WDM file does not contain the column: " & PCode)
            Return Nothing
        End If
        Dim dt As DataTable = MyBase.SetupDataTable(enumDataTableColumns.Date_Result)
        'find the dataset that references that particular station & pcode

        If mGSSHARead Is Nothing Then Initialize2()

        For i As Integer = 0 To mGSSHARead.DataSets.Count - 1
            If (StationID_DS(i) = StationID OrElse StationID_DSAll(i) = StationID) AndAlso PCode_DS(i) = PCode Then
                Dim ts As atcData.atcTimeseries = mGSSHARead.DataSets(i)
                For j As Integer = 0 To ts.numValues - 1
                    Dim d As Date = Date.FromOADate(ts.Dates.Value(j))
                    Dim v As Double = ts.Value(j + 1) 'per email from Mark 12/6/07 need to use next index
                    If Not (Double.IsNaN(v) Or Double.IsInfinity(v)) Then dt.Rows.Add(d, Math.Round(v, 5))
                Next
                Exit For
            End If
        Next
        Return dt
    End Function

    ''' <summary>
    ''' Determine percent of file that has been read
    ''' </summary>
    ''' <returns>Percent of file read</returns>
    Public Overrides Function PercentComplete() As Double
        Return (mRecordNum + 1) * 100.0 / mNumRecords
    End Function

    Private Function Proper(ByVal s As String) As String
        Return My.Application.Culture.TextInfo.ToTitleCase(s)
    End Function

    Public Overrides Function StationIDs() As List(Of String)
        If mGSSHARead Is Nothing Then Initialize2()
        Return MyBase.StationIDs()
    End Function
End Class

